/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: ipc
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-11-04     JasonHu           Init
 */

#include <base/ipc.h>
#include <base/thread.h>
#include <base/process.h>
#define NX_LOG_NAME "ipc"
#include <base/log.h>
#include <base/sharemem.h>
#include <base/debug.h>
#include <base/string.h>
#include <base/malloc.h>

NX_PRIVATE NX_LIST_HEAD(ipcListHead);
NX_PRIVATE NX_SPIN_DEFINE_UNLOCKED(ipcListLock);

NX_IpcServer * NX_IpcSearch(const char * name)
{
    NX_IpcServer * ipc = NX_NULL, * tmp;
    NX_UArch level;

    NX_SpinLockIRQ(&ipcListLock, &level);
    NX_ListForEachEntry (tmp, &ipcListHead, list)
    {
        if (!NX_StrCmp(name, tmp->name))
        {
            ipc = tmp;
            break;
        }
    }
    NX_SpinUnlockIRQ(&ipcListLock, level);
    return ipc;
}

NX_IpcServer * NX_IpcCreateServer(const char * name,
    void * serverHandler, NX_Size maxClient)
{
    NX_IpcServer * ipc;
    NX_UArch level;

    ipc = NX_MemAlloc(sizeof(NX_IpcServer));
    if (!ipc)
    {
        return NX_NULL;
    }

    NX_StrCopy(ipc->name, name);
    ipc->handler = serverHandler;
    ipc->maxClient = maxClient;
    ipc->thread = NX_NULL;
    ipc->process = NX_NULL;
    NX_ListInit(&ipc->clientList);
    NX_SpinInit(&ipc->lock);
    NX_AtomicSet(&ipc->channelCount, 0);

    NX_SpinLockIRQ(&ipcListLock, &level);
    NX_ListAdd(&ipc->list, &ipcListHead);
    NX_SpinUnlockIRQ(&ipcListLock, level);

    return ipc;
}

NX_Error NX_IpcExitServer(NX_IpcServer * ipc)
{
    NX_UArch level;
    NX_IpcChannel * channel, * next;
    NX_Thread * thread;

    if (!ipc)
    {
        return NX_EINVAL;
    }

    /* send signal to all client thread */
    NX_SpinLockIRQ(&ipc->lock, &level);
    NX_ListForEachEntrySafe (channel, next, &ipc->clientList, list)
    {
        thread = channel->clientThread;
        NX_ASSERT(thread);
        NX_SignalSend(thread->tid, NX_SIGNAL_KILL, NX_NULL, NX_NULL);
    }
    NX_SpinUnlockIRQ(&ipc->lock, level);

    /* wait all channel exit */
    while (NX_AtomicGet(&ipc->channelCount) > 0)
    {
        NX_ThreadSleep(100);
    }
    return NX_EOK;
}

NX_Error NX_IpcDestroyServer(NX_IpcServer * ipc)
{
    NX_UArch level;

    if (!ipc)
    {
        return NX_EINVAL;
    }

    NX_SpinLockIRQ(&ipcListLock, &level);
    NX_ListDel(&ipc->list);
    NX_SpinUnlockIRQ(&ipcListLock, level);

    NX_MemFree(ipc);

    return NX_EOK;
}

NX_IpcChannel * NX_IpcCreateChannel(NX_IpcServer * ipc,
    NX_Addr serverBufAddr, NX_Addr clientBufAddr, NX_Size bufSize)
{
    NX_UArch flags;
    NX_IpcChannel * channel;

    channel = NX_MemAlloc(sizeof(NX_IpcChannel));
    if (!channel)
    {
        return NX_NULL;
    }

    /* init channel */
    NX_ListInit(&channel->list);
    channel->serverThread = NX_NULL;
    channel->clientThread = NX_NULL;
    channel->shm = NX_NULL;
    channel->handler = NX_NULL;
    channel->returnValue = 0;
    channel->handler = ipc->handler;
    channel->buf.clientBufAddr = clientBufAddr;
    channel->buf.serverBufAddr = serverBufAddr;
    channel->buf.size = bufSize;
    channel->ipc = ipc;
    channel->state = NX_IPC_CHANNEL_INIT;
    channel->clientSolt = NX_SOLT_INVALID_VALUE;

    /* add channel to ipc */
    NX_SpinLockIRQ(&ipc->lock, &flags);
    NX_ListAdd(&channel->list, &ipc->clientList);
    NX_SpinUnlockIRQ(&ipc->lock, flags);

    return channel;
}

NX_Error NX_IpcDestroyChannel(NX_IpcChannel * channel)
{
    NX_UArch flags;

    if (!channel)
    {
        return NX_EINVAL;
    }

    /* def channel from ipc */
    NX_SpinLockIRQ(&channel->ipc->lock, &flags);
    NX_ListDel(&channel->list);
    NX_SpinUnlockIRQ(&channel->ipc->lock, flags);

    channel->ipc = NX_NULL;
    channel->shm = NX_NULL;
    channel->clientThread = NX_NULL;
    channel->serverThread = NX_NULL;
    channel->handler = NX_NULL;
    
    NX_MemFree(channel);

    return NX_EOK;
}
